package com.gitee.wsl.compose.ui.richtext.content

import androidx.compose.ui.text.style.TextAlign

/**
 * A paragraph style affecting the leading margin.
 *
 * ParagraphContents should be attached from the first
 * character to the last character of a single paragraph.
 */
class ParagraphContent(
    val start: Int,
    val end: Int,
    val firstLeadingMargin: Int? = null,
    val restLeadingMargin: Int? = null,
    val drawer: ParagraphContentDrawer? = null,
    val alignment: TextAlign? = null,
    val lineHeight: Int? = null
)

/**
 * Checks if the given [ParagraphContent] represents a drawer-only configuration.
 *
 * A drawer-only configuration implies that only the drawer content is present in the paragraph,
 * and all other styling properties such as leading margins, alignment, and line height are null.
 * Note that a drawer-only [ParagraphContent] won't add a new ParagraphStyle to an AnnotatedString.
 */
internal fun ParagraphContent.isDrawerOnly(): Boolean {
    return drawer != null &&
            firstLeadingMargin == null &&
            restLeadingMargin == null &&
            alignment == null &&
            lineHeight == null
}

/**
 * Filter the paragraph contents to include items only in the range
 * of [start] (inclusive) and [end] (exclusive).
 *
 * @param start the inclusive start offset of the text range
 * @param end the exclusive end offset of the text range
 */
private fun filterParagraphContents(
    contents: List<ParagraphContent>,
    start: Int,
    end: Int
): List<ParagraphContent> {
    require(start <= end) {
        "start ($start) should be less than or equal to end ($end)"
    }

    return contents.filter { intersect(start, end, it.start, it.end) }.map {
        ParagraphContent(
            firstLeadingMargin = it.firstLeadingMargin,
            restLeadingMargin = it.restLeadingMargin,
            start = maxOf(start, it.start) - start,
            end = minOf(end, it.end) - start,
            drawer = it.drawer
        )
    }.toList()
}

/**
 * Helper function that checks if the range [lStart, lEnd) intersects with the range
 * [rStart, rEnd).
 *
 * @return [lStart, lEnd) intersects with range [rStart, rEnd), vice versa.
 */
private fun intersect(lStart: Int, lEnd: Int, rStart: Int, rEnd: Int) =
    maxOf(lStart, rStart) < minOf(lEnd, rEnd) ||
            contains(lStart, lEnd, rStart, rEnd) || contains(rStart, rEnd, lStart, lEnd)

/**
 * Helper function that checks if the range [baseStart, baseEnd) contains the range
 * [targetStart, targetEnd).
 *
 * @return true if [baseStart, baseEnd) contains [targetStart, targetEnd), vice versa.
 * When [baseStart]==[baseEnd] it return true iff [targetStart]==[targetEnd]==[baseStart].
 */
private fun contains(baseStart: Int, baseEnd: Int, targetStart: Int, targetEnd: Int) =
    (baseStart <= targetStart && targetEnd <= baseEnd) &&
            (baseEnd != targetEnd || (targetStart == targetEnd) == (baseStart == baseEnd))